﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using SLNGP.Common.IoC;
using SLNGP.Core.Fitness;
using SLNGP.Core.Genes;
using SLNGP.Core.GeneticOpearators;
using SLNGP.Core.Individuals;
using SLNGP.Core.Initialization;
using SLNGP.Core.Run;
using SLNGP.Core.SecondaryStrategies;
using SLNGP.Core.StopRun;
using SLNGP.Common.Extensions;
using System.Linq;

namespace SLNGP.Core.GeneticPrograms
{
    public class GeneticProgram : IGeneticProgram
    {
        public GeneticProgram()
        {
            Initializer.Initialize();

            PossibleGenes = new List<IGeneDefinition>();
            GeneticOpearators = new ObservableCollection<IGeneticOpearator>();
            GeneticOpearators.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(GeneticOpearators_CollectionChanged);
            GPStrategies = new Dictionary<Type, IGPStrategy>();

            // TODO: find a better home for these
            this.SetStrategy<IRandomProvider>(new DefaultRandomProvider());
            this.SetStrategy<ISelectRandomGeneDefinition>(new EqualProbablityRandomGeneSelection());
        }

        void GeneticOpearators_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
                foreach (IGeneticOpearator newItem in e.NewItems)
                    newItem.Parent = this;
        }


        public List<IGeneDefinition> PossibleGenes { get; private set; }
        public IFitnessFunction FitnessFunction { get; set; }
        public IGeneticProgrammingRunResults LastRunResults { get; set; }
        public ObservableCollection<IGeneticOpearator> GeneticOpearators { get; set; }
        public IFirstGenerationInitialization FirstGenerationInitialization { get; set; }
        private IStopRunConditions _stopRunConditions;
        public IStopRunConditions StopRunConditions
        {
            get
            {
                if (_stopRunConditions == null)
                {
                    _stopRunConditions = IoC.Get<IStopRunConditions>();
                    _stopRunConditions.Parent = this;
                }
                return _stopRunConditions;
            }
            set
            {
                _stopRunConditions = value;
                _stopRunConditions.Parent = this;
            }
        }
        /// <summary>
        /// Do not consume the GPStrategies property directly. Use the GetStrategy/SetStrategy methods.
        /// </summary>
        public Dictionary<Type, IGPStrategy> GPStrategies { get; set; }

        public void RunUntilStopCondition()
        {
            if (IsValid(true))
                return;

            // TODO: document all runs and not just the last
            LastRunResults = IoC.Get<IGeneticProgrammingRunResults>();

            LastRunResults.Generations.Add(FirstGenerationInitialization.GetFirstGeneration());

            bool _stop = false;
            while (!_stop)
            {
                LastRunResults.Generations.Add(new Generation());

                FitnessFunction.Evaluate(LastRunResults.LastGeneration);

                foreach (IGeneticOpearator geneticOpearator in GeneticOpearators)
                {
                    geneticOpearator.Execute();
                }

                _stop = StopRunConditions.CanStop();
            }
        }

        private bool IsValid(bool throwExceptions)
        {
            if (throwExceptions && FitnessFunction == null)
                throw new GeneticProgrammingException("GeneticProgram.FitnessFunction was not specified. Please set a fitness function that evaulates individuals.");

            if (throwExceptions && FirstGenerationInitialization == null)
                throw new GeneticProgrammingException("GeneticProgram.FirstGenerationInitialization was not specified. Please specify how to initialize the individual population.");

            if (throwExceptions && _stopRunConditions == null)
                throw new GeneticProgrammingException("GeneticProgram.StopRunConditions was not speicified. Please specify a stop condition for the genetic program.");

            return (FitnessFunction == null
                    && FirstGenerationInitialization == null
                    && StopRunConditions == null);
        }

        public void AddPossibleGenes(params IGeneDefinition[] geneDefinitions)
        {
            foreach (IGeneDefinition geneDefinition in geneDefinitions)
                this.PossibleGenes.Add(geneDefinition);
        }

        public T GetStrategy<T>() where T : IGPStrategy
        {
            if (!GPStrategies.ContainsKey(typeof(T)))
                throw new GeneticProgrammingException("Could not retrieve strategy of type " + typeof(T).FullName + ". Are you sure it's set?");

            return (T)GPStrategies[typeof(T)];
        }

        public void SetStrategy<T>(T t) where T : IGPStrategy
        {
            t.Parent = this;
            GPStrategies[typeof(T)] = t;
        }

        public void SetGeneticOpearator<T>(T t) where T : class, IGeneticOpearator
        {
            T replaceThis = (T)GeneticOpearators.FirstOrDefault(g => g is T);

            if (replaceThis != null)
                GeneticOpearators.Replace(t, replaceThis);
            else
                GeneticOpearators.Add(t);
        }

        public T GetGeneticOpearator<T>() where T : class, IGeneticOpearator
        {
            return (T)GeneticOpearators.FirstOrDefault(g => g is T);
        }
    }
}